#include <iostream>
#ifdef _WIN32
#include <Windows.h>
#include <shlobj.h>
#elif __APPLE__
#include <mach-o/dyld.h>
#endif
#include "Poco/File.h"
#include "Poco/DirectoryIterator.h"
#include "Poco/DirectoryWatcher.h"
#include "Poco/Path.h"
#include "Poco/Exception.h"
#include "String/String.h"
#include "Encoding/Encoding.h"
#include "Directory.h"

using namespace SystemTool;

// Create directory by recursive
bool Directory::Create(std::string strDirPath, bool bIsRecursively)
{
    if (strDirPath.empty())
    {
        std::cout << "Dirpath is empty!\n";
        return false;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        if (bIsRecursively)
        {
            FileHelper.createDirectories();
        }
        else
        {
            FileHelper.createDirectory();
        }
        return true;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return false;
}

// Delete directory by recursive
bool Directory::Delete(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        std::cout << "Dirpath is empty!\n";
        return false;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        FileHelper.remove(true);
        return true;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return false;
}

// Rename directory
bool Directory::Rename(std::string strOldDirPath, std::string strNewDirPath, bool bIsOverWrite)
{
    if (strOldDirPath.empty())
    {
        std::cout << "Old dir path is empty!\n";
        return false;
    }

    if (strNewDirPath.empty())
    {
        std::cout << "New dir path is empty!\n";
        return false;
    }

    try
    {
        String strFinalOldDirPath = strOldDirPath;
        strFinalOldDirPath.Replace("\\", "/");

        String strFinalNewDirPath = strNewDirPath;
        strFinalNewDirPath.Replace("\\", "/");

        Poco::File FileHelper(strFinalOldDirPath.Data());
        if (!bIsOverWrite)
        {
            FileHelper.renameTo(strFinalNewDirPath.Data(), Poco::File::OPT_FAIL_ON_OVERWRITE);
        }
        else
        {
            FileHelper.renameTo(strFinalNewDirPath.Data());
        }
        return true;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return false;
}

// Copy directory
bool Directory::CopyTo(std::string strOldDirPath, std::string strNewDirPath)
{
    if (strOldDirPath.empty())
    {
        std::cout << "Old dir path is empty!\n";
        return false;
    }

    if (strNewDirPath.empty())
    {
        std::cout << "New dir path is empty!\n";
        return false;
    }

    try
    {
        String strFinalOldDirPath = strOldDirPath;
        strFinalOldDirPath.Replace("\\", "/");

        String strFinalNewDirPath = strNewDirPath;
        strFinalNewDirPath.Replace("\\", "/");

        Poco::File FileHelper(strFinalOldDirPath.Data());
        FileHelper.copyTo(strFinalNewDirPath.Data());
        return true;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return false;
}

// Move directory
bool Directory::MoveTo(std::string strOldDirPath, std::string strNewDirPath)
{
    if (strOldDirPath.empty())
    {
        std::cout << "Old dir path is empty!\n";
        return false;
    }

    if (strNewDirPath.empty())
    {
        std::cout << "New dir path is empty!\n";
        return false;
    }

    try
    {
        String strFinalOldDirPath = strOldDirPath;
        strFinalOldDirPath.Replace("\\", "/");
        String strFinalNewDirPath = strNewDirPath;
        strFinalNewDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalOldDirPath.Data());
        FileHelper.moveTo(strFinalNewDirPath.Data());
        return true;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return false;
}

// Get dir's all sub-dirs
bool Directory::GetAllSubDirs(std::string strDirPath, std::vector<std::string> &vDirTable)
{
    if (strDirPath.empty())
    {
        std::cout << "Dir path is empty!\n";
        return false;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::DirectoryIterator Iter(strFinalDirPath.Data());
        Poco::DirectoryIterator End;
        std::string strPath;

        while (Iter != End)
        {
            if (Iter->isDirectory())
            {
                strPath = Iter->path();
                vDirTable.push_back(strPath);
                GetAllSubDirs(strPath, vDirTable);
            }
            ++Iter;
        }
        return true;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return false;
}

// Get dir's all sub-files
bool Directory::GetAllSubFiles(std::string strDirPath, std::vector<std::string> &vFileTable)
{
    if (strDirPath.empty())
    {
        std::cout << "Dir path is empty!\n";
        return false;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::DirectoryIterator Iter(strFinalDirPath.Data());
        Poco::DirectoryIterator End;
        std::string strPath;

        while (Iter != End)
        {
            if (Iter->isFile())
            {
                strPath = Iter->path();
                vFileTable.push_back(strPath);
            }
            else
            {
                GetAllSubFiles(Iter->path(), vFileTable);
            }
            ++Iter;
        }
        return true;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return false;
}

// Get temp dir path
std::string Directory::GetTempPath()
{
    try
    {
        return Poco::Path::tempHome();
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return "";
}

// Get appdata dir path
std::string Directory::GetAppDataPath()
{
    std::string strPath = "";
#ifdef _WIN32
    CoInitialize(NULL);
    wchar_t pszPath[MAX_PATH];
    if (SUCCEEDED(SHGetSpecialFolderPath(NULL, pszPath, CSIDL_APPDATA, FALSE)))
    {
        std::wstring strAppData = pszPath;
        strPath = SystemTool::Encoding::UTF16ToUTF8(strAppData);
    }
    CoUninitialize();
    return strPath;
#else
    try
    {
        strPath = Poco::Path::home() + "/Library/Application Support/";
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }
    return strPath;
#endif
}

// Get current work dir path
std::string Directory::GetCurWorkPath()
{
    try
    {
        return Poco::Path::current();
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return "";
}

// Get document dir path
std::string Directory::GetDocumentPath()
{
    try
    {
        std::string strHomePath = Poco::Path::home();
        std::string strDocPath = strHomePath + "/Documents/";
        return strDocPath;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return "";
}

// Get exe's dir path
std::string Directory::GetExcutablePath()
{
#ifdef _WIN32
    wchar_t strExePath[MAX_PATH] = {0};
    if (GetModuleFileName(NULL, strExePath, MAX_PATH) == 0)
    {
        std::cout << "Failed to get exe path\n";
        return "";
    }
    std::string strPath = SystemTool::Encoding::UTF16ToUTF8(strExePath);
    return strPath;
#elif __APPLE__
    char strExePath[PATH_MAX] = {0};
    uint32_t iSize = sizeof(strExePath);
    if (_NSGetExecutablePath(strExePath, &iSize) != 0)
    {
        std::cout << "Failed to get exe path\n";
        return "";
    }
    std::string strPath = strExePath;
    return strPath;
#endif
}

// Trim the dir path
std::string Directory::TrimPath(std::string strPath)
{
    if (strPath.empty())
    {
        return "";
    }

    String strFinalPath = strPath;
    strFinalPath.Replace("\\", "/");
    int iPos = strFinalPath.FindLastOf("/");
    if (iPos == strFinalPath.Length() - 1)
    {
        return strFinalPath.SubStr(0, iPos - 1).Data();
    }
    return strFinalPath.Data();
}

// Add "/" to the dir path and Change dir path's "\\" to "/"
std::string Directory::StandardPath(std::string strPath)
{
    if (strPath.empty())
    {
        return "";
    }

    String strFinalPath = strPath;
    strFinalPath.Replace("\\", "/");
    int iPos = strFinalPath.FindLastOf("/");
    if (iPos == strFinalPath.Length() - 1)
    {
        return strPath;
    }
    strFinalPath = strFinalPath + "/";

    return strFinalPath.Data();
}

// Get dir's total size
size_t Directory::GetDirSize(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        std::cout << "Dir path is empty!\n";
        return 0;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::DirectoryIterator Iter(strFinalDirPath.Data());
        Poco::DirectoryIterator End;
        std::string strPath;
        unsigned long long iTotalSize = 0;

        while (Iter != End)
        {
            if (Iter->isFile())
            {
                strPath = Iter->path();
                Poco::File FilHelper(strPath);
                iTotalSize += FilHelper.getSize();
            }
            else
            {
                iTotalSize += GetDirSize(Iter->path());
            }
            ++Iter;
        }

        return iTotalSize;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return 0;
}

// Get parent dir from a absolution path
std::string Directory::GetParentDirPath(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        return "";
    }

    String strFinalDirPath = strDirPath;
    strFinalDirPath.Replace("\\", "/");
    int iPos = strFinalDirPath.FindLastOf("/");
    if (iPos == -1)
    {
        return strDirPath;
    }

    // Trim the last '/'
    String strPath = strFinalDirPath;
    if (iPos == strFinalDirPath.Length() - 1)
    {
        strPath = strFinalDirPath.SubStr(0, iPos - 1);
    }

    int iPos1 = strPath.FindLastOf("/");
    if (iPos1 == -1)
    {
        return strDirPath;
    }

    return strPath.SubStr(0, iPos1).Data();
}

// Is dir empty
bool Directory::IsEmpty(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        std::cout << "Dir path is empty!\n";
        return false;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::DirectoryIterator Iter(strFinalDirPath.Data());
        Poco::DirectoryIterator End;
        int iSubCnt = 0;

        while (Iter != End)
        {
            ++Iter;
            ++iSubCnt;
        }

        if (iSubCnt == 0)
        {
            return true;
        }

        return false;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return false;
}

// Is dir existed
bool Directory::IsExisted(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        return false;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        return FileHelper.exists();
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return false;
}

// Get dir's time
std::string Directory::GetDirName(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        return "";
    }

    String strFinalDirPath = strDirPath;
    strFinalDirPath.Replace("\\", "/");
    int iPos = strFinalDirPath.FindLastOf("/");
    if (iPos == -1)
    {
        return strDirPath;
    }

    // Trim the last '/'
    String strPath = strFinalDirPath;
    if (iPos == strFinalDirPath.Length() - 1)
    {
        strPath = strFinalDirPath.SubStr(0, iPos - 1);
    }

    int iPos1 = strPath.FindLastOf("/");
    if (iPos1 == -1)
    {
        return strDirPath;
    }

    return strPath.SubStr(iPos1 + 1).Data();
}

// Get dir's time
size_t Directory::GetLastModifyTime(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        return 0;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        return FileHelper.getLastModified().epochMicroseconds() / 1000;
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }

    return 0;
}

// Set dir's time
void Directory::SetLastModifyTime(std::string strDirPath, size_t iModifyTime_Ms)
{
    if (strDirPath.empty())
    {
        return;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        FileHelper.setLastModified(iModifyTime_Ms * 1000);
    }
    catch (const Poco::Exception &e)
    {
        std::cerr << "Err happened:" << e.displayText() << '\n';
    }
}

// Set dir writable
void Directory::SetWritable(std::string strDirPath, bool bIsWritable)
{
    if (strDirPath.empty())
    {
        return;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        FileHelper.setWriteable(bIsWritable);
    }
    catch (Poco::Exception &e)
    {
        std::cout << "Err Happend with:" << e.displayText() << std::endl;
    }
}

// Set dir read only
void Directory::SetReadonly(std::string strDirPath, bool bIsReadable)
{
    if (strDirPath.empty())
    {
        return;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        FileHelper.setReadOnly(bIsReadable);
    }
    catch (Poco::Exception &e)
    {
        std::cout << "Err Happend with:" << e.displayText() << std::endl;
    }
}

// Set dir excutable
void Directory::SetExcutable(std::string strDirPath, bool bIsExcutable)
{
    if (strDirPath.empty())
    {
        return;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        FileHelper.setExecutable(bIsExcutable);
    }
    catch (Poco::Exception &e)
    {
        std::cout << "Err Happend with:" << e.displayText() << std::endl;
    }
}

// Get total space of this disk containing this dir
unsigned long long Directory::GetTotalSpace(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        return 0;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        return FileHelper.totalSpace();
    }
    catch (Poco::Exception &e)
    {
        std::cout << "Err Happend with:" << e.displayText() << std::endl;
    }

    return 0;
}

// Get usable space of this disk containing this dir
unsigned long long Directory::GetUsableSpace(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        return 0;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        return FileHelper.usableSpace();
    }
    catch (Poco::Exception &e)
    {
        std::cout << "Err Happend with:" << e.displayText() << std::endl;
    }

    return 0;
}

// Get free space of this disk containing this dir
unsigned long long Directory::GetFreeSpace(std::string strDirPath)
{
    if (strDirPath.empty())
    {
        return 0;
    }

    try
    {
        String strFinalDirPath = strDirPath;
        strFinalDirPath.Replace("\\", "/");
        Poco::File FileHelper(strFinalDirPath.Data());
        return FileHelper.freeSpace();
    }
    catch (Poco::Exception &e)
    {
        std::cout << "Err Happend with:" << e.displayText() << std::endl;
    }

    return 0;
}
